# /**************************************************************************** 
#  * Meta，一个轻量级的静态分区Hypervisor 
#  *
#  * 版权(c) , 2022-
#  *
#  * 作者:
#  *      Tupelo Shen <shenwanjiang2013@163.com>
#  *
#  * Meta是一个自由软件，你可以在遵循GNU GPLv2协议的条件下，重新修改并发布它。
#  *
#  ***************************************************************************/

SHELL:=bash

PROJECT_NAME:=meta

# 设置交叉编译工具链（此处的环境变量不能使用大写字母代替小写字母，
# 与其它项目的环境变量重复）
cpp=		$(CROSS_COMPILE)cpp
sstrip= 	$(CROSS_COMPILE)strip
cc=			$(CROSS_COMPILE)gcc
ld = 		$(CROSS_COMPILE)ld
as=			$(CROSS_COMPILE)as
objcopy=	$(CROSS_COMPILE)objcopy
objdump=	$(CROSS_COMPILE)objdump
readelf=	$(CROSS_COMPILE)readelf
size=		$(CROSS_COMPILE)size

# Makefile参数及默认值
DEBUG:=y
OPTIMIZATIONS:=2
CONFIG_BUILTIN=n
CONFIG=
PLATFORM=

# submake命令，用来编译VM配置信息
submakes:=config

# 设置工作目录        
# 获取当前工作目录的绝对路径
cur_dir:=$(abspath .)
# 指定meta-hypervisor的源代码目录`src`
src_dir:=$(cur_dir)/src
# 指定CPU架构相关的源码目录`src/arch`
cpu_arch_dir=$(src_dir)/arch
# 指定meta-hypervisor的源代码中使用的自定义库`src/lib`
lib_dir=$(src_dir)/lib
# 指定meta-hypervisor的核心代码目录`src/core`
core_dir=$(src_dir)/core
# 指定平台资源的源代码
platforms_dir=$(src_dir)/platform
# 指定配置信息的目录
configs_dir=$(cur_dir)/configs
# 将配置信息目录设置成一个变量
CONFIG_REPO?=$(configs_dir)

ifeq ($(CONFIG_BUILTIN), y)
ifeq ($(CONFIG),)
 $(error Buil-in configuration enabled but no configuration (CONFIG) specified)
endif
endif

# 必须设置PLATAFORM环境变量（clean伪目标除外）
# 该目录主要是指定平台资源以及驱动程序
ifeq ($(PLATFORM),) 
ifneq ($(MAKECMDGOALS), clean)
 $(error Target platform argument (PLATFORM) not specified)
endif
endif

# 指定某个平台资源和驱动程序相关的目录
platform_dir=$(platforms_dir)/$(PLATFORM)
drivers_dir=$(platforms_dir)/drivers

ifeq ($(wildcard $(platform_dir)),)
 $(error Target platform $(PLATFORM) is not supported)
endif

# 必须设置`ARCH`和`CPU`变量，因为不同的架构和CPU核，其特性不一样。
# `-include`表示如果在将其它makefile文件包含进来，发生错误时，
# 忽略掉error信息。
-include $(platform_dir)/platform.mk
cpu_arch_dir=$(src_dir)/arch/$(ARCH)
cpu_impl_dir=$(cpu_arch_dir)/impl/$(CPU)
-include $(cpu_arch_dir)/arch.mk

# 指定编译目录
build_dir:=$(cur_dir)/build/$(PLATFORM)
builtin_build_dir:=$(build_dir)/builtin-configs
bin_dir:=$(cur_dir)/bin/$(PLATFORM)
ifeq ($(CONFIG_BUILTIN), y)
bin_dir:=$(bin_dir)/builtin-configs/$(CONFIG)
endif
directories:=$(build_dir) $(bin_dir) $(builtin_build_dir) 

# 1. 指定源码目录变量，将$(drivers)添加前缀，是因为platform.mk中，
#    一般只指定具体的驱动文件名称，没有绝对路径
# 2. 每个源码目录下都有一个头文件目录
src_dirs:= $(cpu_arch_dir) $(cpu_impl_dir) $(lib_dir) $(core_dir)\
	$(platform_dir) $(addprefix $(drivers_dir)/, $(drivers))
inc_dirs:=$(addsuffix /inc, $(src_dirs))

# 设置编译的目标对象：在每个源码目录下建立一个objects.mk文件，
# 用于指定编译目标文件
-include $(addsuffix /objects.mk, $(src_dirs))

objs-y:=
objs-y+=$(addprefix $(cpu_arch_dir)/, $(cpu-objs-y))
objs-y+=$(addprefix $(lib_dir)/, $(lib-objs-y))
objs-y+=$(addprefix $(core_dir)/, $(core-objs-y))
objs-y+=$(addprefix $(platform_dir)/, $(boards-objs-y))
objs-y+=$(addprefix $(drivers_dir)/, $(drivers-objs-y))
ifeq ($(CONFIG_BUILTIN), y)
builtin-config-obj:=$(builtin_build_dir)/$(CONFIG).o
objs-y+=$(builtin-config-obj)
endif

deps+=$(patsubst %.o,%.d,$(objs-y))
objs-y:=$(patsubst $(src_dir)%, $(build_dir)%, $(objs-y))

build_dirs:=$(patsubst $(src_dir)%, $(build_dir)%, $(src_dirs) $(inc_dirs))
directories+=$(build_dirs)


# 设置编译目标
targets-y+=$(bin_dir)/$(PROJECT_NAME).elf
targets-y+=$(bin_dir)/$(PROJECT_NAME).bin

# 文件变量，用于记录生成的文件
ld_script:= $(src_dir)/linker.ld
ld_script_temp:= $(build_dir)/linker_temp.ld
deps+=$(ld_script_temp).d

asm_defs_src:=$(cpu_arch_dir)/asm_defs.c
asm_defs_hdr:=$(patsubst $(src_dir)%, $(build_dir)%, \
	$(cpu_arch_dir))/inc/asm_defs.h
inc_dirs+=$(patsubst $(src_dir)%, $(build_dir)%, $(cpu_arch_dir))/inc
deps+=$(asm_defs_hdr).d

gens:=
gens+=$(asm_defs_hdr)

# 指定编译器选项（与架构相关的）
override CPPFLAGS+=$(addprefix -I, $(inc_dirs)) $(arch-cppflags) $(platform-cppflags)
vpath:.=CPPFLAGS

# 是否启用调试
ifeq ($(DEBUG), y)
	debug_flags:=-g
endif

# 覆盖编译选项、汇编选项和链接选项
override CFLAGS+=-O$(OPTIMIZATIONS) -Wall -Werror -ffreestanding -std=gnu11 \
	 -mstrict-align -fno-pic $(arch-cflags) $(platform-cflags) $(CPPFLAGS) \
	 $(debug_flags)

override ASFLAGS+=$(CFLAGS) $(arch-asflags) $(platform-asflags)
override LDFLAGS+=-build-id=none -nostdlib $(arch-ldflags) $(plattform-ldflags)

# 设置伪目标
.PHONY: all
all: $(targets-y)
	
$(bin_dir)/$(PROJECT_NAME).elf: $(gens) $(objs-y) $(ld_script_temp)
	@echo "Linking			$(patsubst $(cur_dir)/%, %, $@)"
	@$(ld) $(LDFLAGS) -T$(ld_script_temp) $(objs-y) -o $@
	@$(objdump) -S --wide $@ > $(basename $@).asm
	@$(readelf) -a --wide $@ > $@.txt

ifneq ($(DEBUG), y)
	@echo "Striping	$@"
	@$(sstrip) -s $@
endif

# 将链接文件拷贝到编译目录的临时链接文件中，注释行删除
$(ld_script_temp):
	@echo "Pre-processing		$(patsubst $(cur_dir)/%, %, $(ld_script))"
	@$(cc) -E $(addprefix -I, $(inc_dirs)) -x assembler-with-cpp $(ld_script) \
		| grep -v '^\#' > $(ld_script_temp)

# 如果伪目标不是clean，则应该将依赖文件包含进来
ifeq (, $(findstring $(MAKECMDGOALS), clean $(submakes)))
-include $(deps)
endif

# 生成链接文件的依赖关系
$(ld_script_temp).d: $(ld_script) 
	@echo "Creating dependecy	$(patsubst $(cur_dir)/%, %, $<)"
	@$(cc) -x assembler-with-cpp  -MM -MT "$(ld_script_temp) $@" \
		$(addprefix -I, $(inc_dirs))  $< > $@

# 对所有源文件建立依赖关系
$(build_dir)/%.d : $(src_dir)/%.[c,S] | $(gens)
	@echo "Creating dependecy	$(patsubst $(cur_dir)/%, %, $<)"
	@$(cc) -MM -MG -MT "$(patsubst %.d, %.o, $@) $@"  $(CPPFLAGS) $< > $@	

# 生成目标对象文件
$(objs-y):
	@echo "Compiling source	$(patsubst $(cur_dir)/%, %, $<)"
	@$(cc) $(CFLAGS) -c $< -o $@

%.bin: %.elf
	@echo "Generating binary	$(patsubst $(cur_dir)/%, %, $@)"
	@$(objcopy) -S -O binary $< $@

# 从arch/$(ARCH)/$(asm_defs_src)文件中，产生汇编宏定义
ifneq ($(wildcard $(asm_defs_src)),)
$(asm_defs_hdr): $(asm_defs_src)
	@echo "Generating header	$(patsubst $(cur_dir)/%, %, $@)"
	@$(cc) -S $(CFLAGS) $< -o - \
		| awk '($$1 == "->") { print "#define " $$2 " " $$3 }' > $@

$(asm_defs_hdr).d: $(asm_defs_src)
	@echo "Creating dependecy	$(patsubst $(cur_dir)/%, %,\
		 $(patsubst %.d,%, $@))"
	@$(cc) -MM -MT "$(patsubst %.d,%, $@)" $(addprefix -I, $(inc_dirs)) $< > $@	
endif

ifdef CONFIG
include $(configs_dir)/configs.mk
all: config
endif

ifeq ($(CONFIG_BUILTIN), y)
override CPPFLAGS+=-DCONFIG_BIN=$(CONFIG_BIN)
builtin-config-src:=$(core_dir)/builtin-config.S
$(builtin-config-obj): $(builtin-config-src) $(CONFIG_BIN)
endif

# 分别为目标对象文件、依赖文件和生成文件创建目录
.SECONDEXPANSION:

$(objs-y) $(deps) $(targets-y) $(gens): | $$(@D)

$(directories):
	@echo "Creating directory	$(patsubst $(cur_dir)/%, %, $@)"
	@mkdir -p $@

# TODO: 测试添加
# include ./test/test.mk
# qemu_cmd:=qemu-system-aarch64
# run:
# 	@$(qemu_cmd) -nographic\
# 		-M virt,secure=on,virtualization=on,gic-version=3 \
# 		-cpu cortex-a53 -smp 4 -m 4G\
# 		-device loader,file="$(PROJECT_NAME).bin",addr=0x50000000,force-raw=on\
# 		-device virtio-net-device,netdev=net0\
# 		-netdev user,id=net0,net=192.168.42.0/24,hostfwd=tcp:127.0.0.1:5555-:22\
# 		-device virtio-serial-device -chardev pty,id=serial3 -device virtconsole,chardev=serial3

# 定义伪目标clean，清除所有的目标对象文件、依赖和生成的文件
.PHONY: clean
clean:
	@echo "Erasing directories..."
	-rm -rf $(build_dir)
	-rm -rf $(bin_dir)
